package oceanlife.pathfinding;

import java.awt.Polygon;
import java.util.ArrayList;
import java.util.HashSet;

public class SimpleGraphBuilder {
	private ArrayList<SimpleObstacle> simpleObs;
    private PointGraph graph;
    
	public SimpleGraphBuilder(ArrayList<SimpleObstacle> simpleObs){
		this.simpleObs = simpleObs;
	}
	
	public ArrayList<Line2> createSimpleGraphLines(){
		ArrayList<Line2> linesConnect = new ArrayList<Line2>();
		HashSet<SimpleObstacle> done = new HashSet<SimpleObstacle>();
		for(SimpleObstacle so1 : simpleObs){
			done.add(so1);
			for(Pnt corner1 : so1.getPathCorners()){
				//to each other point
				for(SimpleObstacle so2 : simpleObs){
					if(done.contains(so2)){
						continue;
					}
					for(Pnt corner2 : so2.getPathCorners()){
						Line2 potLine = new Line2(corner1,corner2);
						//check each obstacle
						boolean overlap = false;
						//check angle optimization
						Line2[] corner1Lines = new Line2[2];
						Line2[] corner2Lines = new Line2[2];
						
						//if border intersect
						if(so1.pathContains(corner2) || so2.pathContains(corner1)){
							overlap = true;
						} else {
							overlapCheck:
							for(SimpleObstacle soOverlap : simpleObs){
								if((!so1.equals(soOverlap) && !so2.equals(soOverlap)) && (soOverlap.pathContains(corner1) || soOverlap.pathContains(corner2))){
									overlap = true;
									break overlapCheck;
								}
								for(Line2 lineOverlap: soOverlap.getPathEdges()){
									Pnt pnt = potLine.intersectionPoint(lineOverlap);
									if(pnt != null){
										if(pnt.equals(corner1)){
											storeCornerLine(corner1Lines,lineOverlap,corner1);
										} else if (pnt.equals(corner2)){
											storeCornerLine(corner2Lines,lineOverlap,corner2);
										} else {
											//remove overlapping lines
											overlap = true;
											break overlapCheck;
										}
										//overlap = true;
										//break overlapCheck;
									}
								}
								/*for(Line2 lineOverlap: soOverlap.getObstacleEdges()){
									Pnt pnt = potLine.intersectionPoint(lineOverlap);
									if(pnt != null){
										if(pnt.equals(corner1)){
											storeCornerLine(corner1Lines,lineOverlap,corner1);
										} else if (pnt.equals(corner2)){
											storeCornerLine(corner2Lines,lineOverlap,corner2);
										} else {
											//remove overlapping lines
											overlap = true;
											break overlapCheck;
										}
									}
								}*/
							}
						}
						if(!overlap){
							//linesConnect.add(new Line2(corner1,corner2));
							
							//if(pathOverlaps){
							//	linesConnect.add(new Line2(corner1,corner2));
							//} else 
							if(corner1Lines[0] == null || corner1Lines[1] == null || corner2Lines[0] == null || corner2Lines[1] == null){
								//System.err.println("didn't find all corner lines");
							} else {
								double[] angles = new double[4];
								double mainAngle = potLine.angle();
								angles[0] = corner1Lines[0].angle();
								angles[1] = corner1Lines[1].angle();
								angles[2] = corner2Lines[0].angle();
								angles[3] = corner2Lines[1].angle();
								double correctionAngle;
								if(mainAngle > 0.0){
									correctionAngle = mainAngle - Math.PI;
									for(int i = 0; i < angles.length;i++){
										if(angles[i] < correctionAngle){
											angles[i] += Math.PI * 2.0;
										}
									}
								} else if(mainAngle < 0.0){
									correctionAngle = mainAngle + Math.PI;
									for(int i = 0; i < angles.length;i++){
										if(angles[i] > correctionAngle){
											angles[i] -= Math.PI * 2.0;
										}
									}
								} else {
									//already lined up correctly
								}
								boolean corner1Correct = false;
								boolean corner2Correct = false;
								if((angles[0] < mainAngle && angles[1] < mainAngle) || (angles[0] > mainAngle && angles[1] > mainAngle)){
									corner1Correct = true;
								}
								if((angles[2] < mainAngle && angles[3] < mainAngle) || (angles[2] > mainAngle && angles[3] > mainAngle)){
									corner2Correct = true;
								}
								if(corner1Correct && corner2Correct){
									linesConnect.add(new Line2(corner1,corner2));
								}
							}
						}
					}
				}
			}
		}
		//add buffer lines that don't intersect with obstacles
		for(SimpleObstacle so1 : simpleObs){
			for(Line2 edge : so1.getPathEdges()){
				boolean overlaps = false;
				for(SimpleObstacle soCheck : simpleObs){
					if(so1.equals(soCheck)){
						continue;
					}
					if(soCheck.intersectsWithObstacle(edge) ||
							soCheck.obstacleToPolygon().contains(edge.getStart().coord[0], edge.getStart().coord[1])||
							soCheck.obstacleToPolygon().contains(edge.getEnd().coord[0], edge.getEnd().coord[1])){ 
						overlaps = true;
						break;
					}
				}
				if(!overlaps){
					linesConnect.add(edge);
				}
			}
		}
		return linesConnect;
	}
	private void storeCornerLine(Line2[] cornerLines, Line2 lineOverlap, Pnt corner) {
		Line2 line = lineOverlap;
		if(!line.getStart().equals(corner)){
			line = new Line2(line.getEnd(),line.getStart());
		}
		if(cornerLines[0] == null){
			cornerLines[0] = line;
		} else if (cornerLines[1] == null){
			cornerLines[1] = line;
		} else {
			System.err.println("more then two corner lines");
		}
	}

	public void makeGraph(ArrayList<Line2> lines) {
	    graph = new PointGraph();
		for(Line2 ent : lines){
			graph.add(ent.getStart());
			graph.add(ent.getEnd());
			graph.addEdge(ent.getStart(), ent.getEnd(), new Edge(new Pnt[]{ent.getStart(),ent.getEnd()}));
		}
		for(Pnt nodeA : graph.getTheNeighbors().keySet()){
			for(Pnt nodeB : graph.getTheNeighbors().get(nodeA).keySet()){
				setMinWidthOnEdge(graph.getTheNeighbors().get(nodeA).get(nodeB));
			}
		}
	}

	public PointGraph getGraph() {
		return graph;
	}

	public void addPoint(Pnt pnt){
		int edgeCount = 0;
		ArrayList<Pnt> newEdgePoints = new ArrayList<Pnt>();
		boolean insidePathArea = false;
		//check if point if inside patharea of object. If so, get closest node to get out of patharea
		for(SimpleObstacle so : simpleObs){
			if(so.pathContains(pnt) && !so.obstacleContains(pnt)){
				insidePathArea = true;
				Pnt closest = so.getPathCorners().get(0);
				for(Pnt potClosest : so.getPathCorners()){				
					if(graph.nodeSet().contains(potClosest) && closest.distanceTo(pnt) > potClosest.distanceTo(pnt)){
						closest = potClosest;
					}
				}
				if(graph.nodeSet().contains(closest)){
					newEdgePoints.add(closest);
				} else {
					System.err.println("closest pathNode not in node set");
				}
			}
		}
		if(!insidePathArea){
			for(Pnt node : graph.nodeSet()){
				Line2 potLine = new Line2(pnt,node);
				boolean pathEdgeCrossed = false;
				lineCheck:
				for(SimpleObstacle so : simpleObs){
					for(Line2 pathEdge : so.getPathEdges()){
						Pnt inter = pathEdge.intersectionPoint(potLine);
						if(inter != null && !(inter.equals(pathEdge.getStart()) || inter.equals(pathEdge.getEnd()))){
							/*if(pathEdge.pointOnLine(inter)){
								if(graph.nodeSet().contains(pathEdge.getStart()) && graph.nodeSet().contains(pathEdge.getEnd())){
									//newEdgePoints.add(pathEdge.getStart());
									//newEdgePoints.add(pathEdge.getEnd());
								}
							}*/
							pathEdgeCrossed = true;
							break lineCheck;
						}
					}
					for(Line2 Edge : so.getObstacleEdges()){
						Pnt inter = Edge.intersectionPoint(potLine);
						if(inter != null && !(inter.equals(Edge.getStart()) || inter.equals(Edge.getEnd()))){
							pathEdgeCrossed = true;
							break lineCheck;
						}
					}
				}
				if(!pathEdgeCrossed){
					newEdgePoints.add(node);
				}
			}
		}
		//might be on one of the pathEdges
		if(newEdgePoints.size() == 0){
			
		}
		if(newEdgePoints.size() > 0){
			graph.add(pnt);
		}
		for(Pnt newEdgePoint : newEdgePoints){
			edgeCount++;
			graph.add(newEdgePoint);
			Edge newEdge = new Edge(new Pnt[]{pnt,newEdgePoint});
			graph.addEdge(pnt, newEdgePoint, newEdge);
			setMinWidthOnEdge(graph.getTheNeighbors().get(pnt).get(newEdgePoint));
		}
	}

	public synchronized void removePoint(Pnt pnt){
		graph.remove(pnt);
	}
	
	private void setMinWidthOnEdge(Edge edge) {
		Pnt fp = edge.getFirstPoint();
		Pnt lp = edge.getLastPoint();
		Pnt middle = fp.getMiddle(lp);
		double minDist = Double.MAX_VALUE;
		for(SimpleObstacle so : simpleObs){
			for(Line2 connectionLine : so.getShortestLinesToObstacles(simpleObs)){
				if(connectionLine.intersectionPoint(new Line2(edge.getFirstPoint(),edge.getLastPoint())) != null && connectionLine.length() < minDist){
					minDist = connectionLine.length();
				}
			}
		}
		edge.setMinimumWidth(minDist);
	}
}
